package ckw.communication;

import java.util.HashMap;
import java.util.List;
import java.util.Random;

import org.jboss.remoting.CannotConnectException;
import org.jboss.remoting.ServerInvoker.InvalidStateException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

import ckw.database.DatabaseService;
import ckw.database.model.Node;
import core.communication.message.CNodeState;
import core.communication.message.CommunicationTestMessage;
import core.communication.message.Direction;
import core.communication.message.Message;
import core.communication.message.NodeStateEnum;
import core.database.NodeType;

@Component
@Scope("prototype")
public class CommunicationChecker implements ApplicationContextAware, Runnable {

	private static final Logger L = LoggerFactory.getLogger(ClientChecker.class);

	private ApplicationContext context;

	private volatile Thread thread = null;
	private Message message;
	private CommunicationService communicationService;
	private DatabaseService databaseService;
	private Random random = new Random();

	private static int packageTimeout = 60000;

	public CommunicationChecker() {
		L.debug("Tworzę CommunicationChecker");

		thread = new Thread(this, "checker");

		L.debug("Utworzono CommunicationChecker");
	}

	public void stop() {
		L.debug("Stop: {} {}", thread.getName(), thread.getId());

		thread = null;
	}

	@Override
	public void run() {
		communicationService = context.getBean(CommunicationService.class);
		databaseService = context.getBean(DatabaseService.class);
		Client client = null;
		while (thread != null) {
			List<Node> nodes = databaseService.getAllNodesByType(NodeType.OKW);
			nodes.addAll(databaseService.getAllNodesByType(NodeType.RKW));
			for (Node node : nodes) {
				message = new CommunicationTestMessage();
				message.setDirection(Direction.DOWN);
				message.setDestinationURI(node.getLocatorURI());
				communicationService.setPath(message);
				client = communicationService.getClients().get(message.getURIFromPath());
				if (client != null)
					try {
						client.send(message);
					} catch (CannotConnectException | InvalidStateException e) {
						// L.debug("Brak połączenia z klientem " + client.toString());
						// Oznaczamy klienta jako nieosiągalnego i
						// uruchamiamy dla niego wątek sprawdzający jego
						// stan
						if (thread == null)
							return;
						checkClient(client);
						resend(message, client);
					} catch (Throwable e) {
						if (thread == null)
							return;
						e.printStackTrace();
					}
			}
			try {
				Thread.sleep(packageTimeout);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
	
	private void checkClient(Client client) {
		// L.debug("Dodaję klienta do sprawdzenia");
		client.setIsWorking(false);
		communicationService.updateNodeState(client.getLocatorURI(), new CNodeState(NodeStateEnum.OFF));
		communicationService.addClientToCheck(client);
	}

	private void resend(Message message, Client client) {
		if (client.getLocatorURI().compareTo(message.getDestinationURI()) != 0) {
			sendRandom(message);
		}
	}

	/**
	 * Wysyła wiadomość do losowego węzła (nie docelowego)
	 * 
	 * @param message
	 *            Wiadomość do wysłania
	 */
	private void sendRandom(Message message) {
		// L.debug("Wysyłam {} do losowego klienta", message.toString());
		Client client = null;
		HashMap<String, Client> clients = communicationService.getClients();
		// Jeżeli mamy tylko jednego klienta to wrzucamy wiadomość z
		// powrotem na kolejkę bo nie ma innych klientów, którzy mogliby ją
		// przyjąć
		if (clients.size() > 1) {
			// Pobieramy losowego klienta
			client = (Client) clients.values().toArray()[random.nextInt(clients.size())];
			// L.debug("Pobrano losowego klienta {}", client.toString());
			if (client.getType() == NodeType.OKW) {
				// L.debug("Losowy klient jest OKW");
				// Sprawdzamy czy klient jest osiągalny
				if (client.getIsWorking()) {
					// L.debug("Losowy klient działa");
					try {
						// L.debug("Próbuje wysłać " + message.toString());
						message.updatePath(client.getLocatorURI());
						client.send(message);
						// Nie możemy połączyć się z klientem
					} catch (CannotConnectException | InvalidStateException e) {
						// L.debug("Brak połączenia z klientem " + client.toString());
						if (thread == null)
							return;
						checkClient(client);
						sendRandom(message);
					} catch (Throwable e) {
						if (thread == null)
							return;
						e.printStackTrace();
					}
				} 
			} 
		}
	}

	@Override
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		this.context = applicationContext;
	}
}
